package bidserver.src;

//Get rid of process ID to make new node join seamlessly.

import java.io.*;
import java.util.*;

class Item {// Item only exists when I have requested CS and not release it.
	boolean readOnly = true; // when myRequestTimeStamp is not infinity, read
	// shows my request is read or write.
	int requestTimeStamp = Constants.INFINITY; // if I requested.
	List<PrintWriter> pendingOKays = new LinkedList<PrintWriter>();
	List<PrintWriter> pendingQ = new LinkedList<PrintWriter>();
}

public class RAReadWriteMutex {
	int srcId; // always make higher id as client and lower id as server
	// so new server can join without restarting old even old config doesn't
	// have new server.
	List<PrintWriter> peers;// all peer servers. a reference to external
	LamportClock c = new LamportClock(); // clock is maintained globally, not
											// per item.
	Map<Integer, Item> items = new HashMap<Integer, Item>(); // item is created
																// on request.
																// if nobody is

	// interest in certain item, then that item is not in the map.

	public RAReadWriteMutex(List<PrintWriter> peers) {
		this.peers = peers;
	}

	public void onDisconnect(PrintWriter pout) {
		synchronized (items) {
			for (Item item : items.values()) {
				synchronized (item) {
					item.pendingQ.remove(pout);
					item.pendingOKays.remove(pout);
					if (item.pendingOKays.isEmpty())
						Util.notify(item);
				}
			}
		}
	}

	public void requestCS(int itemId, boolean readOnly) {
		// Assume itemId is validated externally.
		Util.println("requestCS(" + itemId + "," + readOnly + ")");
		Item item = items.get(itemId);
		if (item == null) {
			item = new Item();
			items.put(itemId, item);
		}
		c.tick();
		item.readOnly = readOnly;
		item.requestTimeStamp = c.getValue();
		Msg msg = new Msg();
		msg.add("Request", "requestCS");
		msg.add("TimeStamp", c.getValue());
		msg.add("ItemId", itemId);
		msg.add("ReadOnly", readOnly);
		msg.add("SrcId", srcId);
		// copy peers to pendingOkays
		synchronized (peers) {
			for (PrintWriter peer : peers) {
				item.pendingOKays.add(peer);
			}
		}
		synchronized (item) {
			for (PrintWriter peer : item.pendingOKays)
				Util.sendMsg(msg, peer);
			while (!item.pendingOKays.isEmpty())
				Util.wait(item);
		}
	}

	public void releaseCS(int itemId) {
		Util.println("releaseCS(" + itemId + ")");
		Item item = items.get(itemId);
		if (item != null) {
			Msg msg = new Msg();
			msg.add("Response", "requestCS");
			msg.add("TimeStamp", c.getValue());
			msg.add("ItemId", itemId);
			synchronized(item){
				while (!item.pendingQ.isEmpty()) {
					PrintWriter pout = item.pendingQ.remove(0);
					Util.sendMsg(msg, pout);
				}
			}
			items.remove(itemId);
		}
	}

	public void onRequestCS(Msg m, int timeStamp, PrintWriter pout) {
		String itemIdStr = m.getValue("ItemId");
		if (itemIdStr == null) {
			Util.println("ItemId missing in requestCS.");
			return;
		}
		int itemId = Integer.parseInt(itemIdStr);
		String srcIdStr = m.getValue("SrcId");
		if (srcIdStr == null) {
			Util.println("SrcId missing in requestCS.");
			return;
		}
		long srcId = Long.parseLong(srcIdStr);

		boolean readOnly = false; // this field is optional.
		String readOnlyStr = m.getValue("ReadOnly");
		if (readOnlyStr != null)
			readOnly = Boolean.parseBoolean(readOnlyStr);

		Item item = items.get(itemId);
		if (item == null || item.requestTimeStamp == Constants.INFINITY || item.requestTimeStamp > timeStamp 
				|| item.requestTimeStamp == timeStamp && this.srcId > srcId || readOnly && item.readOnly) {
			Msg okMsg = new Msg();
			okMsg.add("Response", "requestCS");
			okMsg.add("TimeStamp", c.getValue());
			okMsg.add("ItemId", itemId);
			Util.sendMsg(okMsg, pout);
		} else {
			Util.println("Adding to pendingQ.");
			item.pendingQ.add(pout);
		}
	}

	public void onResponseToRequestCS(Msg m, int timeStamp, PrintWriter pout) {
		String itemIdStr = m.getValue("ItemId");
		if (itemIdStr == null) {
			Util.println("ItemId missing in requestCS.");
			return;
		}
		int itemId = Integer.parseInt(itemIdStr);
		Item item = items.get(itemId);
		if (item != null) {// I'm not interested.
			synchronized (item) {
				item.pendingOKays.remove(pout);
				if (item.pendingOKays.isEmpty())
					Util.notify(item);
			}
		}
	}

	public boolean onMsg(Msg m, PrintWriter pout) {
		String timeStampStr = m.getValue("TimeStamp");
		if (timeStampStr == null)
			return false;// not processing it.
		boolean result = false;
		int timeStamp = Integer.parseInt(timeStampStr);
		c.receiveAction(timeStamp);
		String requestStr = m.getValue("Request");
		if (requestStr != null) {
			if (requestStr.equals("requestCS")) {
				onRequestCS(m, timeStamp, pout);
				result = true;
			}
		} else {
			String responseStr = m.getValue("Response");
			if (responseStr != null && responseStr.equals("requestCS")) {
				onResponseToRequestCS(m, timeStamp, pout);
				result = true;
			}
		}
		return result; // not handling this msg. let caller know.
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub

	}

}
